/*
 * Copyright (C) 2015 - 2020, IBEROXARXA SERVICIOS INTEGRALES, S.L.
 * Copyright (C) 2015 - 2020, Jaume Olivé Petrus (jolive@whitecatboard.org)
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *     * The WHITECAT logotype cannot be changed, you can remove it, but you
 *       cannot change it in any way. The WHITECAT logotype is:
 *
 *          /\       /\
 *         /  \_____/  \
 *        /_____________\
 *        W H I T E C A T
 *
 *     * Redistributions in binary form must retain all copyright notices printed
 *       to any local or remote output device. This include any reference to
 *       Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
 *       appear in the future.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Lua RTOS, a tool for make a LFS file system image
 *
 */

#include "lfs.h"

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <dirent.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>

#define MKLFS_VERSION "0.0.1"
#define LFS_LOOKAHEAD_MAX 128

#ifdef __MINGW32__
#define realpath(N,R) _fullpath((R),(N),_MAX_PATH)
#endif

bool verbose = false;

static struct lfs_config lfs_cfg;
static lfs_t img_lfs;
char *img_fn = "rootfs.lfs"; /* 输出文件的文件名 */
FILE *img_file = NULL;
char root_abs_path[PATH_MAX];

static int lfs_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size)
{
    fseek(img_file, (block * c->block_size) + off, SEEK_SET);
    fread(buffer, 1, size, img_file);
    return 0;
}

static int lfs_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
{
    fseek(img_file, (block * c->block_size) + off, SEEK_SET);
    fwrite(buffer, 1, size, img_file);
    return 0;
}

static int lfs_erase(const struct lfs_config *c, lfs_block_t block)
{
    char *buffer = malloc(c->block_size);
    memset(buffer, 0xFF, c->block_size);
    fseek(img_file, (block * c->block_size), SEEK_SET);
    fwrite(buffer, 1, c->block_size, img_file);
    free(buffer);
    return 0;
}

static int lfs_sync(const struct lfs_config *c)
{
    fflush(img_file);
	return 0;
}

static int create_dir(const char *src)
{
    const char *path;
    int rd;
    lfs_ssize_t err;

    rd = strlen(root_abs_path);
    path = &src[rd];
    if (path) {
        if (verbose == true) {
            printf("dir: %s\r\n", path);
        }

        err = lfs_mkdir(&img_lfs, path);
        if (err < 0) {
            fprintf(stderr,"can't create directory %s: error=%d\r\n", path, err);
            return -1;
		}
	}
    return 0;
}

static int create_file(const char *src)
{
    const char *path;
    int rd;
    int ret = 0;

    rd = strlen(root_abs_path);
    path = &src[rd];
    if (path) {
        if (verbose == true) {
            printf("file: %s\r\n", path);
        }

        // Open source file
        FILE *src_fd = fopen(src, "rb");
        if (!src_fd) {
            fprintf(stderr,"can't open source file %s: errno=%d (%s)\r\n", src, errno, strerror(errno));
            return -1;
        }

        // Open destination file
        lfs_file_t dst_fd;
        lfs_ssize_t err;
        err = lfs_file_open(&img_lfs, &dst_fd, path, LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC);
        if (err < 0) {
            fprintf(stderr,"can't open destination file %s: error=%d\r\n", path, err);
            fclose(src_fd);
            return -1;
        }

        unsigned char dat[1024];
        while (1) {
            rd = fread(dat, 1, 1024, src_fd);
            if (rd > 0) {
                err = lfs_file_write(&img_lfs, &dst_fd, dat, rd);
                if (err < 0) {
                    fprintf(stderr,"can't write to destination file %s: error=%d\r\n", path, err);
                    ret = -1;
                    break;
                }
            } else {
                break;
            }
        }

        // Close destination file
        err = lfs_file_close(&img_lfs, &dst_fd);
        if (err < 0) {
            fprintf(stderr,"can't close destination file %s: error=%d\r\n", path, err);
            ret = -1;
        }

        // Close source file
        fclose(src_fd);
    }

    return ret;
}

static int mklfs(const char *path)
{
    int ret = 0;
    DIR *sub_dir;
    struct dirent *ent;
    char curr_path[PATH_MAX];
    struct stat fstat;

    stat(path, &fstat);
    if (!S_ISDIR(fstat.st_mode)) {
        return -1;
    }
    sub_dir = opendir(path);
    if (sub_dir) {
        while ((ent = readdir(sub_dir))) {
            // Skip . and .. directories
            if ((strcmp(ent->d_name, ".") == 0) || (strcmp(ent->d_name, "..") == 0)) {
                continue;
            }
            // Get the absolute Path
            strcpy(curr_path, path);
            strcat(curr_path, "/");
            strcat(curr_path, ent->d_name);

            stat(curr_path, &fstat);
            if (S_ISDIR(fstat.st_mode)) {
                ret = create_dir(curr_path);
                if (ret != 0) {
                    break;
                }
                ret = mklfs(curr_path);
            } else if (S_ISREG(fstat.st_mode)) {
                ret = create_file(curr_path);
            }
            if (ret != 0) {
                break;
            }
        }

        closedir(sub_dir);
    }
    return ret;
}

static int strendwith(const char *s1, const char *s2)
{
    int l1, l2;
    l1 = strlen(s1);
    l2 = strlen(s2);
    if (l1 < l2) {
        return -1;
    }
    while (l2 > 0) {
        if (s1[l1-1] != s2[l2-1]) {
            return -1;
        }
        l1--, l2--;
    }
    return l1;
}

static void printUsage(const char *appname)
{
    char *name, *bname;
    name = strdup(appname);
    bname = basename(name);
    printf("Version: " MKLFS_VERSION "\r\n"
           "LFS library Version: %d.%d\r\n"
           "Usage: %s [-d <DIR>] [-o <FILE>] -b <SIZE> -C <COUNT> [-c <SIZE>] [-m <SIZE>]\r\n"
           "Make a LFS file system Image from an existing directory tree\r\n"
           "\r\n"
           "OPTIONS:\r\n"
           "    -r, --root <DIR>            Build file system from directory DIR\r\n"
           "    -o, --output <FILE>         Output FILE\r\n"
           "    -b, --block-size <SIZE>     Size of an erasable block in bytes\r\n"
           "    -C, --block-cnt <COUNT>     Maximum erasable block count\r\n"
           "    -c, --cache-size <SIZE>     Size of block caches in bytes\r\n"
           "    -m, --mio-size <SIZE>       Minimum I/O size in bytes\r\n"
           "    -v, --verbose               Enable verbose output\r\n"
           "    -h, --help                  Display this help text\r\n"
           "\r\n"
           "Tips:\r\n"
           "    block size >= cache size >= I/O size\r\n"
           "    SIZE is specified in bytes, but it may also be specified in Kilobytes,"
           " Megabytes, and Gigabytes if a KiB, MiB, or GiB suffix is used.\r\n"
           , LFS_VERSION_MAJOR, LFS_VERSION_MINOR, bname);

    free(name);
}

int mkfs_lfs(int argc, char *argv[])
{
    bool format_only = false;
    char *root_dir = NULL;
    char *img_fname = NULL;
    int block_size = 0, cache_size = 0;
    int read_size = 0, prog_size = 0;
    int block_count = 0;
    int err;

    int arg_c = argc-1;
    char **arg_v = &argv[1];

    verbose = false;

    while (arg_c > 0) {
        if (strcmp(arg_v[0], "--root") == 0 || strcmp(arg_v[0], "-r") == 0) {
            root_dir = arg_v[1];
        } else if (strcmp(arg_v[0], "--output") == 0 || strcmp(arg_v[0], "-o") == 0) {
            img_fname = arg_v[1];
        } else if (strcmp(arg_v[0], "--block-size") == 0 || strcmp(arg_v[0], "-b") == 0) {
            block_size = atoi(arg_v[1]);
            if (strendwith(arg_v[1], "KiB") > 0) {
                block_size *= 1024;
            } else if (strendwith(arg_v[1], "MiB") > 0) {
                block_size *= 1024*1024;
            } else if (strendwith(arg_v[1], "GiB") > 0) {
                block_size *= 1024*1024*1024;
            }
        } else if (strcmp(arg_v[0], "--cache-size") == 0 || strcmp(arg_v[0], "-c") == 0) {
            cache_size = atoi(arg_v[1]);
            if (strendwith(arg_v[1], "KiB") > 0) {
                cache_size *= 1024;
            } else if (strendwith(arg_v[1], "MiB") > 0) {
                cache_size *= 1024*1024;
            } else if (strendwith(arg_v[1], "GiB") > 0) {
                cache_size *= 1024*1024*1024;
            }
        } else if (strcmp(arg_v[0], "--mio-size") == 0 || strcmp(arg_v[0], "-m") == 0) {
            read_size = atoi(arg_v[1]);
            if (strendwith(arg_v[1], "KiB") > 0) {
                read_size *= 1024;
            } else if (strendwith(arg_v[1], "MiB") > 0) {
                read_size *= 1024*1024;
            } else if (strendwith(arg_v[1], "GiB") > 0) {
                read_size *= 1024*1024*1024;
            }
            prog_size = read_size;
        } else if (strcmp(arg_v[0], "--block-cnt") == 0 || strcmp(arg_v[0], "-C") == 0) {
            block_count = atoi(arg_v[1]);
        }else if(strcmp(arg_v[0], "--verbose") == 0 || strcmp(arg_v[0], "-v") == 0){
            verbose = true;
        } else if (strcmp(arg_v[0], "--help") == 0 || strcmp(arg_v[0], "-h") == 0) {
            printUsage(argv[0]);
            return 0;
        } else {
        }
        arg_c--; arg_v++;
    }

    if ((block_size <= 0) || (block_count <= 0)) {
        printUsage(argv[0]);
        return 0;
    }

    if (root_dir == NULL) {
        format_only = true;
    }

    if (read_size <= 0) {
        read_size = block_size;
    }

    if (prog_size < read_size) {
        prog_size = read_size;
    }

    if (cache_size < read_size) {
        cache_size = read_size;
    }

    if (img_fname != NULL) {
        img_fn = img_fname;
    }

    // Mount the file system
    lfs_cfg.read  = lfs_read;
    lfs_cfg.prog  = lfs_prog;
    lfs_cfg.erase = lfs_erase;
    lfs_cfg.sync  = lfs_sync;

    lfs_cfg.block_size  = block_size;
    lfs_cfg.read_size   = read_size;
    lfs_cfg.prog_size   = prog_size;
    lfs_cfg.block_count = block_count;
#if LFS_VERSION >= 0x00020001
    lfs_cfg.block_cycles = -1;
#endif
    lfs_cfg.cache_size  = cache_size;
    lfs_cfg.lookahead_size = LFS_LOOKAHEAD_MAX;
    lfs_cfg.context     = NULL;

    if (verbose == true) {
        printf("LFS library Version: %d.%d\r\n", LFS_VERSION_MAJOR, LFS_VERSION_MINOR);
    }
    if (verbose == true) {
        printf("lfs_config(%p, %p {.context=%p, "
               ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
               ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
               ".block_size=%"PRIu32", .block_count=%"PRIu32", "
               ".block_cycles=%"PRId32", .cache_size=%"PRIu32", "
               ".lookahead_size=%"PRIu32", .read_buffer=%p, "
               ".prog_buffer=%p, .lookahead_buffer=%p, "
               ".name_max=%"PRIu32", .file_max=%"PRIu32", "
               ".attr_max=%"PRIu32"})\n",
               (void*)&img_lfs, (void*)&lfs_cfg, lfs_cfg.context,
               (void*)(uintptr_t)lfs_cfg.read, (void*)(uintptr_t)lfs_cfg.prog,
               (void*)(uintptr_t)lfs_cfg.erase, (void*)(uintptr_t)lfs_cfg.sync,
               lfs_cfg.read_size, lfs_cfg.prog_size, lfs_cfg.block_size, lfs_cfg.block_count,
               lfs_cfg.block_cycles, lfs_cfg.cache_size, lfs_cfg.lookahead_size,
               lfs_cfg.read_buffer, lfs_cfg.prog_buffer, lfs_cfg.lookahead_buffer,
               lfs_cfg.name_max, lfs_cfg.file_max, lfs_cfg.attr_max);
    }

    img_file = fopen(img_fn, "rb+");
    if (img_file == NULL) {
        img_file = fopen(img_fn, "wb+");
    }

    if (img_file == NULL) {
        fprintf(stderr, "Open image failed: errno=%d (%s)\r\n", errno, strerror(errno));
        return -1;
    }

#if 0   // erase full partition image
    unsigned buf[1024];
    int cnt = 0;
    memset(buf, 0xFF, 1024);
    fseek(img_file, 0, SEEK_SET);
    while (cnt < part_size) {
        fwrite(buf, 1, 1024, img_file);
        cnt += 1024;
    }
#endif

    err = lfs_format(&img_lfs, &lfs_cfg);
	if (err < 0) {
        fclose(img_file);
        fprintf(stderr, "LFS format error: error=%d\r\n", err);
		return -1;
	}

    err = lfs_mount(&img_lfs, &lfs_cfg);
	if (err < 0) {
        fclose(img_file);
        fprintf(stderr, "LFS mount error: error=%d\r\n", err);
		return -1;
    }

    if (format_only != true) {
        realpath(root_dir, root_abs_path);
        printf("root: %s; abs: %s\n", root_dir, root_abs_path);
        mklfs(root_abs_path);
    }

//    lfs_dir_t fd_dir;
//    struct lfs_info dir_info;

//    lfs_dir_open(&img_lfs, &fd_dir, "/");
//    while (1) {
//        err = lfs_dir_read(&img_lfs, &fd_dir, &dir_info);
//        if (err <= LFS_ERR_OK) {
//            fprintf(stderr, "LFS dir read: error=%d\r\n", err);
//            break;
//        }
//        printf("%d; %s\n", dir_info.type, dir_info.name);
//    }

    lfs_unmount(&img_lfs);
    fclose(img_file);

	return 0;
}

static void tree(const char *dir, int level)
{
    lfs_dir_t fd_dir;
    struct lfs_info dir_info;
    int err;
    char sub_path[PATH_MAX];

    err = lfs_dir_open(&img_lfs, &fd_dir, dir);
    if (err < LFS_ERR_OK) {
        return;
    }
    while (1) {
        err = lfs_dir_read(&img_lfs, &fd_dir, &dir_info);
        if (err <= LFS_ERR_OK) {
            break;
        }
        if ((strcmp(dir_info.name, ".") == 0) || (strcmp(dir_info.name, "..") == 0)) {
            continue;
        }
        printf("%-*s", level*4, "");
        printf("%s: %s\n", dir_info.type == LFS_TYPE_DIR ? "DIR " : "FILE", dir_info.name);

        if (dir_info.type == LFS_TYPE_DIR) {
            strcpy(sub_path, dir);
            strcat(sub_path, "/");
            strcat(sub_path, dir_info.name);
            tree(sub_path, level + 1);
        }
    }
    lfs_dir_close(&img_lfs, &fd_dir);
}

static void cat(const char *file)
{
    lfs_file_t fd_file;
    int err;
    unsigned char buff[1025];

    err = lfs_file_open(&img_lfs, &fd_file, file, LFS_O_RDONLY);
    if (err != LFS_ERR_OK) {
        fprintf(stderr,"can't open destination file %s: error=%d\r\n", file, err);
        return;
    }
    do {
        err = lfs_file_read(&img_lfs, &fd_file, buff, 1024);
        if (err <= 0) {
            break;
        }
        buff[err] = 0;
        printf("%s", buff);
    } while(1);
    lfs_file_close(&img_lfs, &fd_file);
    printf("\n");
}

int test_lfs(int argc, char *argv[])
{
    char *root_dir = NULL;
    char *img_fname = NULL;
    int block_size = 0, cache_size = 0;
    int read_size = 0, prog_size = 0;
    int block_count = 0;
    int err;

    int arg_c = argc-1;
    char **arg_v = &argv[1];

    verbose = false;

printf("\n******************************\n");
printf("*********** test *************\n");
printf("******************************\n");
    while (arg_c > 0) {
        if (strcmp(arg_v[0], "--root") == 0 || strcmp(arg_v[0], "-d") == 0) {
            root_dir = arg_v[1];
        } else if (strcmp(arg_v[0], "--output") == 0 || strcmp(arg_v[0], "-o") == 0) {
            img_fname = arg_v[1];
        } else if (strcmp(arg_v[0], "--block-size") == 0 || strcmp(arg_v[0], "-b") == 0) {
            block_size = atoi(arg_v[1]);
            if (strendwith(arg_v[1], "KiB") > 0) {
                block_size *= 1024;
            } else if (strendwith(arg_v[1], "MiB") > 0) {
                block_size *= 1024*1024;
            } else if (strendwith(arg_v[1], "GiB") > 0) {
                block_size *= 1024*1024*1024;
            }
        } else if (strcmp(arg_v[0], "--cache-size") == 0 || strcmp(arg_v[0], "-c") == 0) {
            cache_size = atoi(arg_v[1]);
            if (strendwith(arg_v[1], "KiB") > 0) {
                cache_size *= 1024;
            } else if (strendwith(arg_v[1], "MiB") > 0) {
                cache_size *= 1024*1024;
            } else if (strendwith(arg_v[1], "GiB") > 0) {
                cache_size *= 1024*1024*1024;
            }
        } else if (strcmp(arg_v[0], "--mio-size") == 0 || strcmp(arg_v[0], "-m") == 0) {
            read_size = atoi(arg_v[1]);
            if (strendwith(arg_v[1], "KiB") > 0) {
                read_size *= 1024;
            } else if (strendwith(arg_v[1], "MiB") > 0) {
                read_size *= 1024*1024;
            } else if (strendwith(arg_v[1], "GiB") > 0) {
                read_size *= 1024*1024*1024;
            }
            prog_size = read_size;
        } else if (strcmp(arg_v[0], "--block-cnt") == 0 || strcmp(arg_v[0], "-C") == 0) {
            block_count = atoi(arg_v[1]);
        }else if(strcmp(arg_v[0], "--verbose") == 0 || strcmp(arg_v[0], "-v") == 0){
            verbose = true;
        } else if (strcmp(arg_v[0], "--help") == 0 || strcmp(arg_v[0], "-h") == 0) {
            printUsage(argv[0]);
            return 0;
        } else {
        }
        arg_c--; arg_v++;
    }

    if ((block_size <= 0) || (block_count <= 0)) {
        printUsage(argv[0]);
        return 0;
    }

    if (read_size <= 0) {
        read_size = block_size;
    }

    if (prog_size < read_size) {
        prog_size = read_size;
    }

    if (cache_size < read_size) {
        cache_size = read_size;
    }

    if (img_fname != NULL) {
        img_fn = img_fname;
    }

    // Mount the file system
    lfs_cfg.read  = lfs_read;
    lfs_cfg.prog  = lfs_prog;
    lfs_cfg.erase = lfs_erase;
    lfs_cfg.sync  = lfs_sync;

    lfs_cfg.block_size  = block_size;
    lfs_cfg.read_size   = read_size;
    lfs_cfg.prog_size   = prog_size;
    lfs_cfg.block_count = block_count;
#if LFS_VERSION >= 0x00020001
    lfs_cfg.block_cycles = -1;
#endif
    lfs_cfg.cache_size  = cache_size;
    lfs_cfg.lookahead_size = LFS_LOOKAHEAD_MAX;
    lfs_cfg.context     = NULL;

    if (verbose == true) {
        printf("LFS library Version: %d.%d\r\n", LFS_VERSION_MAJOR, LFS_VERSION_MINOR);
        printf("lfs_config(%p, %p {.context=%p, "
               ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
               ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
               ".block_size=%"PRIu32", .block_count=%"PRIu32", "
               ".block_cycles=%"PRId32", .cache_size=%"PRIu32", "
               ".lookahead_size=%"PRIu32", .read_buffer=%p, "
               ".prog_buffer=%p, .lookahead_buffer=%p, "
               ".name_max=%"PRIu32", .file_max=%"PRIu32", "
               ".attr_max=%"PRIu32"})\n",
               (void*)&img_lfs, (void*)&lfs_cfg, lfs_cfg.context,
               (void*)(uintptr_t)lfs_cfg.read, (void*)(uintptr_t)lfs_cfg.prog,
               (void*)(uintptr_t)lfs_cfg.erase, (void*)(uintptr_t)lfs_cfg.sync,
               lfs_cfg.read_size, lfs_cfg.prog_size, lfs_cfg.block_size, lfs_cfg.block_count,
               lfs_cfg.block_cycles, lfs_cfg.cache_size, lfs_cfg.lookahead_size,
               lfs_cfg.read_buffer, lfs_cfg.prog_buffer, lfs_cfg.lookahead_buffer,
               lfs_cfg.name_max, lfs_cfg.file_max, lfs_cfg.attr_max);
    }

    img_file = fopen(img_fn, "rb");

    if (img_file == NULL) {
        fprintf(stderr, "Open image failed: errno=%d (%s)\r\n", errno, strerror(errno));
        return -1;
    }

    err = lfs_mount(&img_lfs, &lfs_cfg);
    if (err < 0) {
        fclose(img_file);
        fprintf(stderr, "LFS mount error: error=%d\r\n", err);
        return -1;
    }

    printf("> tree /\n");
    tree("/", 0);

    printf("\n> cat /Makefile\n");
    cat("/Makefile");

    lfs_unmount(&img_lfs);
    fclose(img_file);

    return 0;
}
